package org.xteam.camllight;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;

import org.xteam.camllight.io.Channel;

public class VirtualMachine {
	
	public static void main(String[] args) throws FileException, IOException {
		CamlFile fd = null;
		try {
			fd = attempt_open(args[0], false);
		} catch (FileException e) {
			int i = 0;
			try {
				fd = attempt_open(args[i], true);
			} catch (FileException ee) {
				System.out.println();
				System.exit(2);
			}
		}
		byte[] code = fd.readCode();
		Block globals = fd.readGlobal();
		sys_init(globals, new String[0]);
		
		BytecodeDecoder decoder = new BytecodeDecoder(globals);
		decoder.decode(code);
	}
	
	private static int sys_var_init[] = {
		  0400, 0200, 0100,
		  0040, 0020, 0010,
		  0004, 0002, 0001,
		  04000, 02000,
		  0444, 0222, 0111
		};
	
	private static void sys_init(Block globals, String[] argv) {
		globals.setField(Globals.SYS__COMMAND_LINE, copy_string_array(argv));
		for (int i = Globals.SYS__S_IRUSR; i <= Globals.SYS__S_IXALL; i++) {
			globals.setField(i, Value.valLong(sys_var_init[i - Globals.SYS__S_IRUSR]));
		}
		globals.setField(Globals.SYS__INTERACTIVE, Value.ValFalse);
		globals.setField(Globals.SYS__MAX_VECT_LENGTH, Value.valLong(Options.Max_wosize));
		globals.setField(Globals.SYS__MAX_STRING_LENGTH, Value.valLong(Value.Bsize_wsize (Options.Max_wosize) - 1));
		globals.setField(Globals.SYS__WORD_SIZE, Value.valLong(32));
	}

	private static Object copy_string_array(String[] argv) {
		if (argv.length == 0) {
			return Atom.get(0);
		}
		Block block = new Block(0, argv.length);
		for (int i = 0; i < argv.length; ++i) {
			block.setField(i, argv[i]);
		}
		return block;
	}

	private static class CamlFile {

		private RandomAccessFile fd;
		private ExecTrailer trailer;
		private String name;

		public CamlFile(String name, RandomAccessFile fd, ExecTrailer trailer) {
			this.name = name;
			this.fd = fd;
			this.trailer = trailer;
		}
		
		public Block readGlobal() {
			Channel chan = new Channel(fd);
			Block globals = (Block)Intern.internVal(chan);
			globals.setField(Globals.GLOBAL_DATA, globals);
			
			// type ('a, 'b) t = { mutable max_len: int; mutable data: ('a, 'b) bucketlist vect }
			// type ('a, 'b) bucketlist = Empty | Cons of 'a * 'b * ('a, 'b) bucketlist
			// type qualified_ident = { qual: string; id: string }
			// type numtable = { mutable num_cnt: int ; mutable num_tbl: ('a, int) hashtbl__t }
			// qualified_ident numtable
			Object globalTable = Intern.internVal(chan);
			
			// (qualified_ident * int) numtable
			Object exnTagTable = Intern.internVal(chan);
			
			// (qualified_ident * int) vect
			Object tagExnTable = Intern.internVal(chan);
			
			// type event_kind = Lbefore | Lafter of typ
			// type access_path = Path_root | Path_son of int * access_path | Path_tuple of access_path list
			// type lambda_variable = { var_name: string; var_path: access_path; var_typ: typ }
			// type transl_env = Tnullenv | Treserved of transl_env | Tenv of lambda_variable list * transl_env
			// type event = { ev_kind : event_kind; ev_file: string; ev_char: int; ev_env: transl_env; mutable ev_pos: int }
			// event list
			Object events = Intern.internVal(chan);
			
			chan.close();
			return globals;
		}

		public byte[] readCode() throws FileException {
			byte[] code = new byte[trailer.code_size];
			try {
				long pos = fd.length() - (ExecTrailer.TRAILER_SIZE + trailer.code_size + trailer.data_size + trailer.symbol_size + trailer.debug_size);
				fd.seek(pos);
				if (fd.read(code) != code.length) {
					throw new FileException(TRUNCATED_FILE);
				}
			return code;
			} catch (IOException e) {
				throw new FileException(TRUNCATED_FILE);
			}
		}
		
	}
	
	private static class FileException extends Exception {

		private int number;

		public FileException(int number) {
			this.number = number;
		}
		
	}
	
	private static class TrailerStream implements IStream {

		private byte[] buffer;
		private int index;

		public TrailerStream(byte[] buffer) {
			this.buffer = buffer;
			this.index = 0;
		}

		private long read(int size, boolean isBigEndian) {
			long value = 0;
			if (isBigEndian) {
				for (int i = 0; i < size; ++i) {
					value = (value<<8) | get();
				}
			} else {
				for (int i = 0; i < size; ++i) {
					value = value | (get()<<(i*8));
				}
			}
			return value;
		}
		
		private int get() {
			return buffer[index++] & 0xff;
		}

		public int readUI32BE() {
			return (int) read(4, true);
		}
		
	}
	
	public static final int FILE_NOT_FOUND = -1;
	public static final int TRUNCATED_FILE = -2;
	public static final int BAD_MAGIC_NUM  = -3;
	
	private static ExecTrailer read_trailer(RandomAccessFile fd) throws FileException {
		ExecTrailer trail = new ExecTrailer();
		byte[] buffer = new byte[ExecTrailer.TRAILER_SIZE];

		try {	
			fd.seek(fd.length() - ExecTrailer.TRAILER_SIZE);
			if (fd.read(buffer) < ExecTrailer.TRAILER_SIZE) {
				throw new FileException(TRUNCATED_FILE);
			}
		} catch (IOException e) {
			throw new FileException(TRUNCATED_FILE);
		}
		trail.read(new TrailerStream(buffer));
		if (trail.magic == ExecTrailer.EXEC_MAGIC) {
			return trail;
		} else {
			throw new FileException(BAD_MAGIC_NUM);
		}
		
	}
	
	private static CamlFile attempt_open(String name, boolean do_open_script) throws FileException {
		String truename = Sys.searchpath(name);
		if (truename == null) {
			truename = name;
		} else {
			// *name = truename;
		}
		RandomAccessFile fd;
		
		try {
			fd = new RandomAccessFile(truename, "r");
		} catch (FileNotFoundException e) {
			throw new FileException(FILE_NOT_FOUND);
		}
			
		if (!do_open_script){
			byte[] buf = new byte[2];
			try {
				int n = fd.read (buf);
				if (n < 2) {
					try {
						fd.close();
					} catch (IOException e) {
					}
					throw new FileException(TRUNCATED_FILE);
				}
			} catch (IOException e) {
				throw new FileException(TRUNCATED_FILE);
			}
			if (buf [0] == '#' && buf [1] == '!') {
				try {
					fd.close();
				} catch (IOException e) {
				}
				throw new FileException(BAD_MAGIC_NUM);
			}
		}
		try {
			ExecTrailer trail = read_trailer(fd);
			return new CamlFile(truename, fd, trail);
		} catch (FileException e) {
			try {
				fd.close();
			} catch (IOException ee) {
			}
			throw e;
		}
	}

}
